/**
 * Copyright (C), 2018 store
 * Encoding: UTF-8
 * Date: 19-6-14 上午10:45
 * History:
 */
package com.swak.frame.hystrix;

import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.hystrix.contrib.javanica.utils.AopUtils;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.swak.frame.command.annotation.SwakExecutionType;
import com.swak.frame.exception.ApiException;
import com.swak.frame.hystrix.annotation.SwakHystrix;
import com.swak.frame.hystrix.command.SwakHystrixCommand;
import com.swak.frame.hystrix.exception.SwakFallBackException;


/**
 * SwakHystrixCommandAspect.java
 *
 * @author ColleyMa
 * @version 19-9-30 上午11:40
*/
@Aspect
public class SwakHystrixCommandAspect {
    protected Logger logger = LoggerFactory.getLogger(SwakHystrixCommandAspect.class);

    @Pointcut("@annotation(com.swak.frame.hystrix.annotation.SwakHystrix)")
    public void swakHystrixPointcut() {
    }

    @Around("swakHystrixPointcut()")
    public Object swakHystrixCommand(ProceedingJoinPoint joinPoint)
        throws Throwable {
        Method method = AopUtils.getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);

        SwakHystrix swakHystrix = method.getAnnotation(SwakHystrix.class);

        if (swakHystrix == null) {
            return joinPoint.proceed();
        }
        try {
            SwakExecutionType executionType = SwakExecutionType.getExecutionType(method.getReturnType(), swakHystrix.async());

            // HystrixCommand
            SwakHystrixCommand command = new SwakHystrixCommand(joinPoint, executionType,swakHystrix);
            
            switch (executionType) {
            case SYNCHRONOUS:return command.execute();

            case ASYNCHRONOUS:return command.queue();

            case FORCE_ASYNC:return command.queue().get();

            default:
                throw new RuntimeException("unsupported execution type: " + executionType);
            }
        } catch (HystrixRuntimeException e) {
        	logger.error("swakHystrixCommand execution exception:"+e.getMessage(), e);
            throw propagateCause(e);
        } catch (ExecutionException e) {
            Throwable ex = e.getCause();
            if ((ex != null) && ex instanceof HystrixRuntimeException) {
                throw propagateCause((HystrixRuntimeException) ex);
            }
            Throwable rootException = ExceptionUtils.getRootCause(e);
            if(rootException==null) {
            	throw e;
            }
            throw rootException;
        }
    }

    private Throwable propagateCause(HystrixRuntimeException throwable) {
        Throwable fallbackEx = throwable.getFallbackException();
        if (fallbackEx != null) {
            return fallbackEx;
        }
        Throwable cause = throwable.getCause();
        if ((cause!=null) && cause instanceof ApiException) {
            return cause;
        }
        return new SwakFallBackException("No fallback available,deal with by oneself", cause);
    }
}
